/**
 * TAC AgentWare
 * http://www.sics.se/tac        tac-dev@sics.se
 *
 * Copyright (c) 2001-2005 SICS AB. All rights reserved.
 *
 * SICS grants you the right to use, modify, and redistribute this
 * software for noncommercial purposes, on the conditions that you:
 * (1) retain the original headers, including the copyright notice and
 * this text, (2) clearly document the difference between any derived
 * software and the original, and (3) acknowledge your use of this
 * software in pertaining publications and reports.  SICS provides
 * this software "as is", without any warranty of any kind.  IN NO
 * EVENT SHALL SICS BE LIABLE FOR ANY DIRECT, SPECIAL OR INDIRECT,
 * PUNITIVE, INCIDENTAL OR CONSEQUENTIAL LOSSES OR DAMAGES ARISING OUT
 * OF THE USE OF THE SOFTWARE.
 *
 * -----------------------------------------------------------------
 *
 * Author  : Joakim Eriksson, Niclas Finne, Sverker Janson
 * Created : 23 April, 2002
 * Updated : $Date: 2005/06/07 19:06:16 $
 *	     $Revision: 1.1 $
 * ---------------------------------------------------------
 */

package jcn2000;
import se.sics.tac.util.ArgEnumerator;
import java.util.logging.*;
import javax.swing.Timer;
import se.sics.tac.aw.*;
import java.util.Vector;
import java.awt.event.*;
import java.io.*;

//FIXME: Add the callbacks to update our knowledge of the world (auctions succeeding or failing etc.)
public class JCN2000 extends AgentImpl {
/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++/
/--------------------------GLOBAL VARIABLES--------------------------/
/+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/

  //Old variabeles
  private static final Logger log = Logger.getLogger(JCN2000.class.getName());

  /*
  Constants, (can be called throughout the package)
  *****************************************************
  !!Don't use the values directly,always use the name!!
  *****************************************************
  keep in mind that the TAC agentware uses several different ones for the same 
  items. The funny numbering is to allow for a nice trick with the entertainment
  constants.
  */
  /*
  When dealing with the TAC agent, most values can be converted using the translate() function
  */
  static final int INFLIGHT = 0;
  static final int OUTFLIGHT = 1;

  static final int CHEAP_HOTEL = 2;
  static final int EXPENSIVE_HOTEL = 3;

  static final int ENT_AW = 4;
  static final int ENT_AP = 5;
  static final int ENT_MU = 6;

  //Categories
  static final int CAT_FLIGHT = 9;
  static final int CAT_HOTEL = 10;
  static final int CAT_ENTERTAINMENT = 12;
      
  static final boolean DEBUG = false;
  static final boolean TEST = false;
  static final int CLIENT_AMOUNT = 8;
  
  //translation categories
  static final int CLIENT_PREFERENCE = 13;
  static final int DAYS = 14;
  static final int TAC_TYPES = 15;
  static final int TAC_CATEGORY = 16;
  static final int BID_TIME = 5;
  //Timer for bidding
  private Timer biddingTimer;
  private int cycle;
  
  private tacClient[] Clients = new tacClient[CLIENT_AMOUNT];

  private FileOutputStream xmlFile;
  private PrintStream xml;
  private String errors = "";
  /*The allocation container keeps track of the tickets we have, whom they have 
  been assigned to, and whether they have already been bid on. it is updated 
  every new bid cycle.
  There is one for cheap hotels, expensive hotels, flights and each entertainment category*/
  AllocationContainer allocations[] = new AllocationContainer[7];

  /*****************************************************************************
                               MAINLOOP FUNCTIONS
  *****************************************************************************/

  //executed when the timer ends
  ActionListener doActions = new ActionListener() {
    public void actionPerformed(ActionEvent evt) {
      cycle++;
      if (DEBUG || TEST){
        dbg("****************************************************");
        dbg("                  step"+cycle+" at "+(agent.getGameTime()/1000));
        dbg("****************************************************");
      }
      if (cycle > 1){
        xml.println("</cycle>");
      }
      xml.println("<cycle n=\'"+cycle+"\' t='"+(agent.getGameTime()/1000)+"\'>");
      xml.println(errors);
      errors = "";
      calculateAndUpdate();
    }
  };

  //function to start-up the agent
  public static void main (String[] args) {
  	TACAgent.main(args);
  }
  
  /*
   Simple variable initialisation, most other init like things will happen in the 
   gameStarted callback
  */
  protected void init(ArgEnumerator args) {
    allocations[ENT_AW] = new AllocationContainer(CAT_ENTERTAINMENT);
    allocations[ENT_AP] = new AllocationContainer(CAT_ENTERTAINMENT);
    allocations[ENT_MU] = new AllocationContainer(CAT_ENTERTAINMENT);

    allocations[CHEAP_HOTEL] = new AllocationContainer(CAT_HOTEL);
    allocations[EXPENSIVE_HOTEL] = new AllocationContainer(CAT_HOTEL);

    allocations[INFLIGHT] = new AllocationContainer(CAT_FLIGHT);
    allocations[OUTFLIGHT] = new AllocationContainer(CAT_FLIGHT);
    
    for (int i=0; i<CLIENT_AMOUNT; i++){
      Clients[i] = new tacClient();
    }

    //We want it to go of every minute
    biddingTimer = new Timer(60*1000, doActions);
    //But only from the 45th second onwards
    biddingTimer.setInitialDelay(10*1000);
  }

  /*****************************************************************************
                               CALLBACK FUNCTIONS
  *****************************************************************************/
  
  
  //On the start of the game
  //All the setup and clearing of variables should happen here.
  public void gameStarted() {
    try
    {
      xmlFile = new FileOutputStream ("game_"+agent.getGameID()+".xml");
      xml = new PrintStream(xmlFile);
      xml.println("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>");
      xml.println("<?xml-stylesheet type=\"text/xsl\" href=\"../TAC.xsl\"?>");
    }
    catch (Exception e){
      System.err.println ("Error writing to file");
    }
    if (DEBUG || TEST){
      dbg("NEW GAME STARTED! ("+agent.getGameID()+")");
    }
    
    xml.println("<game id=\'"+agent.getGameID()+"\'>");
    xml.println("<allocations>");
    for (int type=0;type<7;type++){
      xml.println("<allocation id=\'"+type+"\' name=\'"+constant(type)+"\' />");
    }    
    xml.println("</allocations>");
    
    //clear and set the variables
    createClientArray();
    
    XMLtestClientPreferences();
    
    for (int type=0;type<7;type++){
      allocations[type].clear();
      for (int n=0; n<4; n++){
      	int day = n + (type == OUTFLIGHT ? 1 : 0);
        allocations[type].bidReceived(day,agent.getOwn(getAuctionFor(type,day)),0);
      }
    }
    //You get a penalty of 200 for selling entertainment you don't own, so 
    //selling at 210 gives a guaranteed profit
    /*for (int n=0; n<4; n++){
      placeBid(ENT_AW,n,-10,210f);
      placeBid(ENT_AP,n,-10,210f);
      placeBid(ENT_MU,n,-10,210f);
    }  */

    cycle = (int)agent.getGameTime()/60000;
    if (cycle > 0 ){
      calculateAndUpdate();
    }
    //FIXME: Allow for a 15 second delay at the start.
    //calculateAndUpdate();
    //start the engine
    biddingTimer.start();
  }
  
  
  /*On game stop*/
  public void gameStopped() {
    //Kill running processess
    biddingTimer.stop();
    xml.println(errors);
    errors = "";
    xml.println("</cycle></game>");
    try{
      xmlFile.close();
      xml.close();
    }
    catch (Exception e){
      System.err.println ("Error writing to file");
    }
  }

  //When we receive an update about a quote
  public void quoteUpdated(Quote quote) {
    int item[] = translateBack(quote.getAuction());
    int type = item[0];
    int day  = item[1];
    errors += "<quoteUpdate time=\'"+agent.getGameTime()+"\' type=\'"+type+"\' day=\'"+day+"\' askprice=\'"+quote.getAskPrice()+"\' bidprice=\'"+quote.getBidPrice()+"\' hqw=\'"+quote.getHQW()+"\'/>";
    //System.out.println("'"+constant(type)+"';'"+day+"';'"+quote.getAskPrice()+"';'"+quote.getBidPrice()+"';'"+agent.getGameTime()+"'");
    //dbg(constant(type)+"("+day+"): updated askprice="+quote.getAskPrice()+" prevprice="+allocations[type].getBidPrice(day)+" HQW="+quote.getHQW());
    //HQW only works for hotels/entertainment auctions that we bade on.
    //For flights the HQW is easily discovered by comparing our bidPrice to the current askPrice
    //FIXME: after this works: remove everything up to the last if
    if ((type == INFLIGHT) || (type == OUTFLIGHT)) {
      if (quote.getAskPrice() > allocations[type].getBidPrice(day)){
  	    allocations[type].updateHQW(day,0);
  	  }
  	  else {
  	    allocations[type].updateHQW(day,quote.getBid().getQuantity());
  	  }
  	}
  	else {
  	  if (quote.hasHQW(quote.getBid())){
  	    allocations[type].updateHQW(day,quote.getHQW());
  	  }
  	}
  	allocations[type].updatePrice(day, quote.getAskPrice(), agent.getGameTime());
  	//quote.setNextQuoteTime(4000);
  }
  

  //update status to notify that the bid is rejected, updates the allocationContainer
  public void bidRejected(Bid bid) {
    int item[] = translateBack(bid.getAuction());
    int type = item[0];
    int day  = item[1];
    allocations[type].bidReceived(day,0,0);
    errors +="<error class='bidRejected' time=\'"+agent.getGameTime()+"\' type=\'"+type+"\' day=\'"+day+"\' Reason=\'" + bid.getRejectReasonAsString()+"\' />";
    if (DEBUG) {
      dbg(constant(type)+" bid rejected for day "+day+" Reason: " + bid.getRejectReasonAsString());
    }
  }
  
  //When an auction is closed we simply clear the bids, if we received stuff
  //at the acution a seperate transaction function is called as well
  public void auctionClosed(int auction){
    int item[] = translateBack(auction);
    int type = item[0];
    int day  = item[1];
    if (DEBUG) {
      dbg(constant(type)+" closed for day "+(day));
    }
    xml.println("<auctionClose time=\'"+agent.getGameTime()+"\' type=\'"+type+"\' day=\'"+day+"\' />");
    allocations[type].bidReceived(day,0,0);
  }
  
  public void bidError(Bid bid,int error){
    int item[] = translateBack(bid.getAuction());
    int type = item[0];
    int day  = item[1];
      dbg("ERROR:The bid errorred out! ("+agent.commandStatusToString(error)+")");

    errors +="<error class=\'bidError\' time=\'"+agent.getGameTime()+"\' type=\'"+type+"\' day=\'"+day+"\' Reason=\'"+agent.commandStatusToString(error)+"\'/>";
  }
  
  public void bidUpdated(Bid bid){
    //dbg("The bid is updated!");
  }
  
  public void transaction(Transaction transaction) {
    int item[] = translateBack(transaction.getAuction());
    int type = item[0];
    int day  = item[1];
    if (DEBUG) {
      dbg(constant(type)+"day "+day+" bid received");
    }
    allocations[type].bidReceived(day, transaction.getQuantity(), transaction.getPrice()*transaction.getQuantity());
  }


  /*****************************************************************************
                                   STRATEGY
  *****************************************************************************/

  /*
  From here the we calculate everything and determine what new stuff needs to be bought
  or sold, we update our bids here and terminate to wait another minute
  */
  private void calculateAndUpdate(){
    for (int i=0; i<7; i++){
      //All bets are off. Nobody has any rights to anything anymore
      allocations[i].clearAssignments();
    }
    for (int i=0; i<CLIENT_AMOUNT; i++){
      //calculate the most profitable dayspan
      calculateDaySpan(Clients[i]);
    }/*
    if (TEST){
    	executeTest();
    }*/
    sendBids();
  	executeXMLTest();
  }
  
  /*
  Calculate the most profitable day span per client.
  */
  private float calculateDaySpan(tacClient client){
    //FIXME: add entertainment (it should be inserted everywhere, within the value calculation etc.)
    int arrival     = -1;
    int departure   = -1;
    int hotelType   = CHEAP_HOTEL;
    int profit[]    = new int[20];
    int tripCounter = 0;
    int bestDay     = -1;
    client.clearSettings();
    //If we're already optimal then we're done (this is just an efficiency shortcut)
    /*
    vary the arrival date, and the departure date
    this gives us 4+3+2+1=10 possible trips, with two hotels that makes 20.
    We calculate _all_ possible combinations, some are impossible (auction 
    closed etc.) and will get a value of -1
    */
    for (int arr=0; arr<4; arr++){
      for (int dep=arr+1;dep<5; dep++){
        profit[tripCounter]= clientValue(client,arr,dep,CHEAP_HOTEL);
      	tripCounter++;
        profit[tripCounter]= clientValue(client,arr,dep,EXPENSIVE_HOTEL);
      	tripCounter++;
      }
    }
    //get the number of the highest profit value, bestDay goes from 0-19
    bestDay = keySort(profit)[0];
    /*
    If the key is odd, then we got the most profit in the second statement,
    which is the expensive hotel.
    */
    if (bestDay % 2 == 1){
      hotelType = EXPENSIVE_HOTEL;
    }
    /*
    halve the value because paired each two have the same arr and dep value 
    now bestDay goes from 0-9
    */
    bestDay /= 2;
    /*
    Using the index value we can retrace what the values of arr and dep belong
    to that value (we could also have used a two simple arrays, but I liked 
    this better)
    */
    switch(bestDay){
      case 0:  
      case 1:  
      case 2:  
      case 3:  
        arrival=0;
        departure = 1+bestDay;
        break;
      case 4:  
      case 5:  
      case 6:  
        arrival=1;
        departure = 2+bestDay-4;
        break;
      case 7:  
      case 8: 
        arrival=2;
        departure = 3+bestDay-7;
        break;
      case 9: 
        arrival=3;
        departure = 4;
        break;
    }
    /*
    Now we have an arrival, a departure and a hotelType.
    Time to update the allocation containers to indicate that we want them
    */
    //Only place bids that are actually going to be profitable
    //profit[0] now contains maximum profit
    client.pValue = clientValue(client,client.pArrive,client.pDepart,EXPENSIVE_HOTEL);
    client.cValue  =profit[0];
    if (profit[0] > 0) {
    	client.isParticipating = true;
      allocations[INFLIGHT].request(arrival);
      allocations[OUTFLIGHT].request(departure);
      for (int i=arrival; i<departure;i++){
        allocations[hotelType].request(i);
      }
      client.cEntValue = entValue(client,arrival,departure,true);
      if (client.entAWD >=0){
        allocations[ENT_AW].request(client.entAWD);
      }
      if (client.entAPD >=0){
        allocations[ENT_AP].request(client.entAPD);
      }
      if (client.entMUD >=0){
        allocations[ENT_MU].request(client.entMUD);
      }
      //store the values for future use
      client.cArrive = arrival;
      client.cDepart = departure;
      client.hotelType = hotelType;
    }
    else {
    	client.isParticipating = false;
    }
    return profit[0];
  }
  
  /*
  This function checks whether is is possible to construct a package for a 
  specific day span. (maybe the auctions are already closed etc.)
  */
  private boolean isPossible(int arrive, int depart, int hotelType){
    Quote quote;
    for (int day=arrive; day<depart; day++){
      quote = agent.getQuote(getAuctionFor(hotelType,day));
      /*a day is impossible if we both:
      haven't got any hoteltickets and if the auction is closed (or if we closed it for a whatif question*/
      if (!allocations[hotelType].whatIf(day)) {
        if (allocations[hotelType].getInventory(day) <= 0 && quote.isAuctionClosed()){
          return false;
        }
      }
    }
    return true;
  }
  
  //Calculate the Value of a certain arrival-departure span
  private int clientValue(tacClient client, int arrive, int depart, int hotelType){
    int Value;
    float cost;
    int entVal;
    if (!(isPossible(arrive, depart, hotelType))){
      Value = -1;
    }
    else {
      Value = client.utility(arrive, depart, hotelType);
      entVal = entValue(client,arrive,depart, false);
      Value += entVal;
      if (arrive == client.pArrive && depart == client.pDepart){
        client.pEntValue = entVal;
      }
      //We subtract from this what we have to pay:
      for (int day=arrive; day<depart; day++){
        cost = allocations[hotelType].cost(day);
        Value -= (int)cost;
      }
      //We don't buy flights until day 5, so their price shouldn't influence desiscions until then
      if (cycle > BID_TIME){
        Value -= allocations[INFLIGHT].cost(arrive);
        Value -= allocations[OUTFLIGHT].cost(depart);
      }
    }
    return Value;
  }
  
  private int entValue(tacClient client, int arrive, int depart, boolean save){
      int maxVal = 0;
      int val = 0;
      int AWD = -1;
      int APD = -1;
      int MUD = -1;
      for (int e1=arrive; e1<depart+1; e1++){
        for (int e2=arrive; e2<depart+1; e2++){
          if (e2 == depart || e2 != e1){
            for (int e3=arrive; e3<depart+1; e3++){
              if ((e3 == depart ) || ((e3 != e2) && (e3 != e1))){
                val = 0;
                if (e1 < depart){
                  val += (client.enAWVal - allocations[ENT_AW].cost(e1));
                }
                if (e2 < depart){
                  val += (client.enAPVal - allocations[ENT_AP].cost(e2));
                }
                if (e3 < depart){
                  val += (client.enMUVal - allocations[ENT_MU].cost(e3));
                }
                if (val > maxVal) {
                  AWD = (e1 < depart ? e1 : -1);
                  APD = (e2 < depart ? e2 : -1);
                  MUD = (e3 < depart ? e3 : -1);
                  maxVal = val;
                }
              }
            }
          }
        }
      }
      if (save){
        client.entAWD = AWD;
        client.entAPD = APD;
        client.entMUD = MUD;
      }
      return maxVal;
  }
  
 /* Hotels require a bid, without a proper knowledge of the other bids. The 
  * lowest quote is know, but it often happens I am that lowest quote (with a
  * value of 1.0) So that is useless. Furthermore, I only pay what the lowest 
  * bid is bidding. This leads to the following strategy: 
  * a) Bid 1.0
  * b) If HQW < bidQuantity then bid "zero-profit" value.
  * This function calculates the profit that is lost by only getting hqw instead
  * of bidquantity. This will give us the value we need add to the current auction price
  * (the auction price is already subtracted from the prices).
  */
  private float hotelValue(int day, int type){
    float curValue = 0;
    float whatIfValue = 0;
    int auction = getAuctionFor(type,day);
    float retVal;
    //First calculate the current profit
    for (int i=0; i<CLIENT_AMOUNT; i++){
      curValue += Clients[i].cValue;
    }
    for (int i=0; i<7;i++){
      allocations[type].whatIf(true, (i==type ? day:-1));
    }
    allocations[type].clearAssignments();
    //then calculate the whatif profit
    for (int i=0; i<CLIENT_AMOUNT; i++){
      //calculate the most profitable dayspan
      whatIfValue += calculateDaySpan(Clients[i]);
    }
    for (int i=0; i<7;i++){
      allocations[type].whatIf(false, -1);
    }
    retVal = (curValue - whatIfValue)/(int)allocations[type].getBidsToPlace(day)[0];
    dbg(constant(type)+" ("+day+"): "+allocations[type].getMarketPrice(day)+"+("+curValue+" - "+whatIfValue+")/ "+(int)allocations[type].getBidsToPlace(day)[0]+"="+allocations[type].getMarketPrice(day)+"+"+retVal);
    if (retVal < 1){
      retVal = 1;
    }
    return allocations[type].getMarketPrice(day) + retVal;
  }
  
  /*This code issues/updates the bids*/
  private void sendBids(){
    int day;
    float[] bid = new float[2];
    //for each type
    for (int i=0; i<7;i++){
      //for each day
      for (int d=0; d<4;d++){
        //I want to place bids on day 0-3 except for OUTFLIGHTs which go
        //from day 1-4
        day = d + (i == OUTFLIGHT ? 1 : 0);
        
        bid = allocations[i].getBidsToPlace(day);
        if ((int)bid[0] > 0){
          if (i==EXPENSIVE_HOTEL || i==CHEAP_HOTEL){
            if (bid[0] > 0){
              bid[1] = hotelValue(day,i);
              placeBid(i,day,(int)bid[0],bid[1],"func=\'hotelValue\'");
            }
          }
          else {
            if (cycle > BID_TIME){
              if (bid[0] > 0){
                placeBid(i,day,(int)bid[0],bid[1],"func=\'getBidsToPlace\'");
              }
            }
            else {
              allocations[i].updateBid(d,0,0);
            }
          }
        }
      }
    }
  }
  
  private void placeBid(int type, int day, int qty, float price, String func){
    int auction = getAuctionFor(type, day);
    xml.println("<placeBid qty=\'"+qty+"\' price=\'"+price+"\' type=\'"+type+"\' day=\'"+day+"\' time=\'"+agent.getGameTime()+"\' "+func+" />");
    if (price < 0){
      return;
    }
    Bid bid = new Bid(auction);
    bid.addBidPoint(qty, price);
    Bid oldbid = agent.getBid(auction);
    if (oldbid==null){
      agent.submitBid(bid);
    }
    else {
      agent.replaceBid(oldbid,bid);
    }
    if (DEBUG) {
      dbg("Buying "+qty+" at "+price+" for "+constant(type)+" at day "+translate(DAYS,day)+"("+auction+")");
    }
    allocations[type].updateBid(day, qty,price);
  }
  
  /*
  This will create an array of tacClient objects, sorted by worthness.
  iterating over it 0->CLIENT_AMOUNT will give the worthiest first, then the
  second worthiest etc.
  FIXME: done by checking max(utility), could be improved by checking against
  max(possible(utility)) and rerunning every calulateAndUpdate()
  */
  private void createClientArray(){
    int[] values= new int[CLIENT_AMOUNT];
    //set up the array
    for (int i=0; i<CLIENT_AMOUNT; i++){
      Clients[i].clear();
      Clients[i].pArrive=agent.getClientPreference(i, translate(CLIENT_PREFERENCE,INFLIGHT))-1;//day correction
      Clients[i].pDepart=agent.getClientPreference(i, translate(CLIENT_PREFERENCE,OUTFLIGHT))-1;//day correction
      Clients[i].expHVal=agent.getClientPreference(i, translate(CLIENT_PREFERENCE,EXPENSIVE_HOTEL));
      Clients[i].enAWVal=agent.getClientPreference(i, translate(CLIENT_PREFERENCE,ENT_AW));
      Clients[i].enMUVal=agent.getClientPreference(i, translate(CLIENT_PREFERENCE,ENT_MU));
      Clients[i].enAPVal=agent.getClientPreference(i, translate(CLIENT_PREFERENCE,ENT_AP));
      values[i] = Clients[i].worth();
    }
    //reverse sort the worth values
    values = keySort(values);
    //refill the array in order of how much they are worth
    for (int n=0; n<CLIENT_AMOUNT; n++){
      int i = values[n];
      Clients[i].clear();
      Clients[i].pArrive=agent.getClientPreference(i, translate(CLIENT_PREFERENCE,INFLIGHT))-1;//day correction
      Clients[i].pDepart=agent.getClientPreference(i, translate(CLIENT_PREFERENCE,OUTFLIGHT))-1;//day correction
      Clients[i].expHVal=agent.getClientPreference(i, translate(CLIENT_PREFERENCE,EXPENSIVE_HOTEL));
      Clients[i].enAWVal=agent.getClientPreference(i, translate(CLIENT_PREFERENCE,ENT_AW));
      Clients[i].enMUVal=agent.getClientPreference(i, translate(CLIENT_PREFERENCE,ENT_MU));
      Clients[i].enAPVal=agent.getClientPreference(i, translate(CLIENT_PREFERENCE,ENT_AP));
      
    }
  }
  
  //returns an array with the indexes of the reversely sorted array
  //So keySort([20,10,25,1]) returns [2,0,1,3]
  //also reversely sorts the array itself
  private int[] keySort(int arr[]) {
    int[] a = arr;
    int[] ret = new int[a.length];
    for (int i = 0; i<a.length; i++) {
      ret[i] = i;
    }
    for (int i = a.length; --i>=0; ) {
      boolean flipped = false;
      for (int j = 0; j<i; j++) {
        if (a[j] < a[j+1]) {
          int T = a[j];
          a[j] = a[j+1];
          a[j+1] = T;
          T=ret[j];
          ret[j] = ret[j+1];
          ret[j+1] = T;
          flipped = true;
        }
      }
      if (!flipped) {
        return ret;
      }
    }
    return ret;
  }
  /*****************************************************************************
                            TRANSLATION FUNCTIONS
  *****************************************************************************/
  /*
  These functions translates our constants to the tac agent ones.
  
  The different categories are:
  TAC_TYPES          : hotel/flights/entertainment
  TAC_CATEGORY       : cheap/expensive hotel, in/out flight, ent_aw/ap/mu
  CLIENT_PREFERENCES : same as category, but now for preferences
  DAYS               : conversion between tac 1-based en our 0-based system

  Please use our own constants + this function because it makes the code more
  readable and prevents bugs by having only one word for each concept. (ENT_AW 
  can be either E1 or TYPE_ALLIGATOR_WRESTLING and INFLIGHT can be either 
  ARRIVAL or TYPE_INFLIGHT for example)
  */
  
  private int getAuctionFor(int type, int day){
    return agent.getAuctionFor(translate(TAC_CATEGORY,type), translate(TAC_TYPES,type), translate(DAYS,day));
  }
  
  private int translateBack(int category, int type){
  	if ((category == TACAgent.CAT_FLIGHT) && (type == TACAgent.TYPE_INFLIGHT))
  	  return INFLIGHT;
  	if ((category == TACAgent.CAT_FLIGHT) && (type == TACAgent.TYPE_OUTFLIGHT))
  	  return OUTFLIGHT;

  	if ((category == TACAgent.CAT_HOTEL) && (type == TACAgent.TYPE_CHEAP_HOTEL))
  	  return CHEAP_HOTEL;
    if ((category == TACAgent.CAT_HOTEL) && (type == TACAgent.TYPE_GOOD_HOTEL))
  	  return EXPENSIVE_HOTEL;
  	  
    if ((category == TACAgent.CAT_ENTERTAINMENT) && (type == TACAgent.TYPE_ALLIGATOR_WRESTLING))
  	  return ENT_AW;
    if ((category == TACAgent.CAT_ENTERTAINMENT) && (type == TACAgent.TYPE_AMUSEMENT))
  	  return ENT_AP;
    if ((category == TACAgent.CAT_ENTERTAINMENT) && (type == TACAgent.TYPE_MUSEUM))
  	  return ENT_MU;
    dbg("ERROR:Translate went wrong!");
    return -1;
  }
  
  //returns the type and the 
  private int[] translateBack(int auction){
    int[] retVal = new int[2];
    retVal[0] = translateBack(agent.getAuctionCategory(auction), agent.getAuctionType(auction));
    retVal[1] = agent.getAuctionDay(auction)-1;//day correction
    return retVal;
  }

  private int translate(int type, int value){
    String error;
    switch (type){
      case CLIENT_PREFERENCE:
        switch(value){
          case ENT_AW:
            return TACAgent.E1;
          case ENT_AP:
            return TACAgent.E2;
          case ENT_MU:
            return TACAgent.E3;
          case EXPENSIVE_HOTEL:
            return TACAgent.HOTEL_VALUE;
          case INFLIGHT:
            return TACAgent.ARRIVAL;
          case OUTFLIGHT:
            return TACAgent.DEPARTURE;
          case CAT_FLIGHT:
            return TACAgent.CAT_FLIGHT;
          case CAT_HOTEL:
            return TACAgent.CAT_HOTEL;
          case CAT_ENTERTAINMENT:
            return TACAgent.CAT_ENTERTAINMENT;
          default:
            error = "Value:"+value+" can not be translated to a client preference";
            break;
        }
        break;
      case TAC_TYPES:
        switch(value){
          case ENT_AW:
            return TACAgent.TYPE_ALLIGATOR_WRESTLING;
          case ENT_AP:
            return TACAgent.TYPE_AMUSEMENT;
          case ENT_MU:
            return TACAgent.TYPE_MUSEUM;
          case CHEAP_HOTEL:
            return TACAgent.TYPE_CHEAP_HOTEL;
          case EXPENSIVE_HOTEL:
            return TACAgent.TYPE_GOOD_HOTEL;
          case INFLIGHT:
            return TACAgent.TYPE_INFLIGHT;
          case OUTFLIGHT:
            return TACAgent.TYPE_OUTFLIGHT;
          default:
            error = "Value:"+value+" can not be translated to a TAC type.";
            break;
        }
        break;
      case DAYS:
        return value+1;//day correction
      case TAC_CATEGORY:
        switch(value){
          case ENT_AW:
            return TACAgent.CAT_ENTERTAINMENT;
          case ENT_AP:
            return TACAgent.CAT_ENTERTAINMENT;
          case ENT_MU:
            return TACAgent.CAT_ENTERTAINMENT;
          case CHEAP_HOTEL:
            return TACAgent.CAT_HOTEL;
          case EXPENSIVE_HOTEL:
            return TACAgent.CAT_HOTEL;
          case INFLIGHT:
            return TACAgent.CAT_FLIGHT;
          case OUTFLIGHT:
            return TACAgent.CAT_FLIGHT;
          default:
            error = "Value:"+value+" can not be translated to a TAC type.";
            break;
        }
        break;
      default:
        error = "Category:"+value+" does not exist for stranslation.";
        break;
    }
    dbg("ERROR:"+error);
    return -1;
  }

  /*****************************************************************************
                               DEBUG FUNCTIONS
  *****************************************************************************/
/*  
  public void executeTest(){
  	tst("****Running Tests");
  	testClientSpan();
  	//testAllocationItems();
    tst("");
    tst("I have assigned:");
    testGetAssigned();
    tst("");
    tst("I have:");
    testGetInventory();
    tst("");
    tst("I probably have(/will have):");
    testGetProbably();
    tst("");
    tst("So my inventory is:");
    testGetInv();
    tst("");
    tst("So I'm buying:");
    testGetBids();
    //tst("");
    //tst("I think the prices will be");
    //testGetPrices();
  }

  
  public void testGetAssigned(){
		for (int i=0; i<7;i++){
		  tst(constant(i),false);
		  for (int day=0;day<5;day++){
		    System.out.print(", "+allocations[i].dbgGetAssigned(day));
		  }
      System.out.println("");  
    }  
  }  

  public void testGetPrices(){
		for (int i=0; i<7;i++){
		  for (int day=0;day<5;day++){
  		  tst(constant(i)+" ("+day+") "+allocations[i].predictedPrice(day));
  		  tst("Bidding :             "+allocations[i].predictedPrice(day));
		  }
      System.out.println("");  
    }  
  }  

  public void testGetInventory(){
		for (int i=0; i<7;i++){
		  tst(constant(i),false);
		  for (int day=0;day<5;day++){
		    System.out.print(", "+allocations[i].dbgGetInventory(day));
		  }
      System.out.println(""); 
    }    
  }  

  public void testGetInv(){
		for (int i=0; i<7;i++){
		  tst(constant(i),false);
		  for (int day=0;day<5;day++){
		    System.out.print(", "+allocations[i].getInventory(day));
		  }
      System.out.println(""); 
    }    
  }  
  
  public void testGetProbably(){
		for (int i=0; i<7;i++){
		  tst(constant(i),false);
		  for (int day=0;day<5;day++){
		    System.out.print(", "+(allocations[i].dbgGetHQW(day)));
		  }
      System.out.println(""); 
    }
  }
  
  public void testGetBids(){
    float[] b = new float[2];
    boolean valid;
		for (int i=0; i<7;i++){
		  valid = true;
		  tst(constant(i),false);
		  for (int day=0;day<5;day++){
		    if (allocations[i].getBid(day)[0] > 0 && (allocations[i].getInventory(day) + allocations[i].getBid(day)[0]) != 0){
          valid = false;
		    }
  		  b = allocations[i].getBid(day);
  		  System.out.print(", "+(int)b[0]);
  		}
  		if (!valid){
        System.out.print(" = ERROR! Bids do not match inventory");  
      }
      System.out.println("");
    }
    
  }  

  // Fucntion to test if the allocationcontainer know how to handle added and removed items
  public void testAllocationItems(){
    Quote quote;
		for (int i=0; i<7;i++){
   		tst(constant(i)+" : "+allocations[i].getInventory(0)+", "+allocations[i].getInventory(1)+", "+allocations[i].getInventory(2)+", "+allocations[i].getInventory(3)+", "+allocations[i].getInventory(4));
 			tst("                   "+allocations[i].cost(0)+", "+allocations[i].cost(1)+", "+allocations[i].cost(2)+", "+allocations[i].cost(3)+", "+allocations[i].cost(4));
 		}
  	for (int day=0; day<4;day++){
		  quote = agent.getQuote(agent.getAuctionFor(TACAgent.CAT_HOTEL, TACAgent.TYPE_CHEAP_HOTEL, day+1));
      tst("CH "+day+" is "+(quote.isAuctionClosed() ? "CLOSED" : "open"));
      quote = agent.getQuote(agent.getAuctionFor(TACAgent.CAT_HOTEL, TACAgent.TYPE_GOOD_HOTEL, day+1));
      tst("EH "+day+" is "+(quote.isAuctionClosed() ? "CLOSED" : "open"));
		}
  }
  
  public void testClientSpan(){
    tst("  0--1--2--3--4	                 util	Valu	AW	AP	MU");
    for (int i=0;i<CLIENT_AMOUNT;i++){
    	if (Clients[i].isParticipating){
    	  printClientAllocation(i, Clients[i].cArrive, Clients[i].cDepart, Clients[i].hotelType, Clients[i].cValue);
    	  printClientAllocation(i, Clients[i].pArrive, Clients[i].pDepart, EXPENSIVE_HOTEL, Clients[i].pValue);
    	}
    	else {
      	tst(""+i);
      	tst(""+i);
    	}
      tst("");
    }
  }
  public void printClientAllocation(int i, int cArrive, int cDepart, int hotelType, int value){
    tst(i+" ",false);
    for (int s=0;s<cArrive;s++){
      System.out.print("   ");
    }
    System.out.print("I-x");
    for (int s=cArrive+1;s<cDepart;s++){
      System.out.print("--x");
    }
    System.out.print("O  ");
    for (int s=cDepart;s<4;s++){
      System.out.print("   ");
    }
    System.out.print("	"+constant(hotelType));
    System.out.print(Clients[i].utility(cArrive,cDepart,hotelType,Clients[i].entAWD > -1,Clients[i].entAPD > -1,Clients[i].entMUD > -1)+"	");
    System.out.print(value+"	");
    if (Clients[i].entAWD > -1){
      System.out.print(Clients[i].entAWD+"	");
    }
    else {
      System.out.print(" 	");
    }
    if (Clients[i].entAPD > -1){
      System.out.print(Clients[i].entAPD+"	");
    }
    else {
      System.out.print(" 	");
    }
    if (Clients[i].entMUD > -1){
      System.out.print(Clients[i].entMUD+"	");
    }
    else {
      System.out.print(" 	");
    }
    System.out.println("");
  }
*/
  public void executeXMLTest(){
    XMLtestClientSpan();
    XMLtestGetAssigned();
    XMLtestGetInventory();
    XMLtestGetProbably();
    XMLtestGetInv();
    //XMLtestGetBids();
    //XMLtestGetPrices();
    for (int i=0; i<7;i++){
      xml.println("<allocation id=\""+i+"\">");
      allocations[i].dbgTstAllocationContainer(xml);
      xml.println("</allocation>");
    }
    
  }


  public void XMLtestGetAssigned(){
    xml.print("<assigned>");
		for (int i=0; i<7;i++){
      xml.print("<assigned ");
		  xml.print("id=\'"+i+"\' ");
		  for (int day=0;day<5;day++){
		    xml.print("d"+day+"=\""+allocations[i].dbgGetAssigned(day)+"\" ");
		  }
      xml.println("/>");
    }
    xml.print("</assigned>");
  }  

/*  public void XMLtestGetPrices(){
    xml.print("<prices>");
		for (int i=0; i<7;i++){
      xml.print("<prices ");
		  xml.print("id=\'"+i+"\' ");
		  for (int day=0;day<5;day++){
		    xml.print("d"+day+"=\""+allocations[i].predictedPrice(day)+"\" ");
		  }
      xml.println("/>");
    }
    xml.print("</prices>");
  }  */

  public void XMLtestGetInventory(){
    xml.print("<actualInventory>");
		for (int i=0; i<7;i++){
      xml.print("<actualInventory ");
		  xml.print("id=\'"+i+"\' ");
		  for (int day=0;day<5;day++){
		    xml.print("d"+day+"=\""+allocations[i].dbgGetInventory(day)+"\" ");
		  }
      xml.println("/>");
    }
    xml.print("</actualInventory>");
  }  

  public void XMLtestGetInv(){
    xml.print("<calcInventory>");
		for (int i=0; i<7;i++){
      xml.print("<calcInventory ");
		  xml.print("id=\'"+i+"\' ");
		  for (int day=0;day<5;day++){
		    xml.print("d"+day+"=\""+allocations[i].getInventory(day)+"\" ");
		  }
      xml.println("/>");
    }
    xml.print("</calcInventory>");
  }  
  
  public void XMLtestGetProbably(){
    xml.print("<INVnHQW>");
		for (int i=0; i<7;i++){
      xml.print("<INVnHQW ");
		  xml.print("id=\'"+i+"\' ");
		  for (int day=0;day<5;day++){
		    xml.print("d"+day+"=\""+allocations[i].dbgGetHQW(day)+"\" ");
		  }
      xml.println("/>");
    }
    xml.print("</INVnHQW>");
  }
/*  
  public void XMLtestGetBids(){
    xml.print("<bid>");
    float[] b = new float[2];
		for (int i=0; i<7;i++){
      xml.print("<bid ");
		  xml.print("id=\'"+i+"\' ");
		  for (int day=0;day<5;day++){
		    xml.print("d"+day+"=\""+(int)allocations[i].getBid(day)[0]+"\" ");
		  }
      xml.println("/>");    
    }
    xml.print("</bid>");
  }  
*/
  public void XMLtestClientSpan(){
    xml.println("<dayspans>");
    for (int i=0;i<CLIENT_AMOUNT;i++){
    	if (Clients[i].isParticipating){
    	  xml.println("<client id=\'"+i+"\' arrive=\'"+Clients[i].cArrive+"\' depart=\'"+Clients[i].cDepart+"\' hotel=\'"+Clients[i].hotelType+"\' value=\'"+Clients[i].cValue+"\' AW=\'"+Clients[i].entAWD+"\' AP=\'"+Clients[i].entAPD+"\' MU=\'"+Clients[i].entMUD+"\' />");
    	}
    	else {
      	xml.println("<client id=\'"+i+"\' />");
    	}
    	xml.println("<explanation id=\'"+i+"\' "+Clients[i].explanation()+"/>");
    }
    xml.println("</dayspans>");
  }
  
  public void XMLtestClientPreferences(){
    xml.println("<clientpreferences>");
    for (int i=0;i<CLIENT_AMOUNT;i++){
   	  xml.println("<client id=\'"+i+"\' arrive=\'"+Clients[i].pArrive+"\' depart=\'"+Clients[i].pDepart+"\' hotel=\'"+Clients[i].expHVal+"\' AW=\'"+Clients[i].enAWVal+"\' AP=\'"+Clients[i].enAPVal+"\' MU=\'"+Clients[i].enMUVal+"\' />");
    }
    xml.println("</clientpreferences>");
  }
  
  public void dbg(String str){
    	System.out.println("***DEBUG: "+str);
  }

  public void dbg(String str, boolean End){
    if (End){
    	System.out.println("***DEBUG: "+str);
  	}
  	else {
  	  System.out.print("***DEBUG: "+str);
  	}
  }

  public void tst(String str){
    	System.out.println("***TEST:  "+str);
  }

  public void tst(String str, boolean End){
    if (End){
    	System.out.println("***TEST:  "+str);
  	}
  	else {
  	  System.out.print("***TEST:  "+str);
  	}
  }
  public String constant(int i){
  	switch(i){
    case ENT_AW:
      return "ENT_AW           ";
    case ENT_AP:
      return "ENT_AP           ";
    case ENT_MU:
      return "ENT_MU           ";
    case EXPENSIVE_HOTEL:
      return "EXPENSIVE_HOTEL  ";
    case CHEAP_HOTEL:
      return "CHEAP_HOTEL      ";
    case INFLIGHT:
      return "INFLIGHT         ";
    case OUTFLIGHT:
      return "OUTFLIGHT        ";
    case CAT_FLIGHT:
      return "CAT_FLIGHT       ";
    case CAT_HOTEL:
      return "CAT_HOTEL        ";
    case CAT_ENTERTAINMENT:
      return "CAT_ENTERTAINMENT";
    default:
    	return "Unknown Constant";
    }
  }
}
